Các vấn đề của C C_(ngôn_ngữ_lập_trình)

Một câu nói phổ biến được người ta lập lại nhiều lần của một nhà thiết kế trình dịch Bjarne Stroustrup, người sáng lập ra C++, là "C makes it easy to shoot yourself in the foot." (tạm dịch: "C làm cho việc bạn tự hại bạn trở nên dễ dàng") . Nói cách khác, C cho phép nhiều phép toán không mong muốn trong một cách tổng quát, và do đó, nhiều lỗi đơn giản đã được tạo ra bởi một người lập trình mà chúng lại không thể phát hiện qua trình dịch hay ngay cả không phát hiện ra trong lúc thi hành. Điều này là nguyên nhân của một số chương trình có các ứng xử không lường trước được và có các lỗ hổng về an toàn. Một dị bản ngôn ngữ C là Cyclone điều chỉnh được một phần trong số các vấn đề như vậy.

Một lý do của các vấn đề nêu trên là để tránh cho cái giá quá cao phải trả cho việc kiểm soát (lỗi) ở thời gian dịch và thời gian thi hành. Một lý do khác là sự đòi hỏi để giữ C được càng hiệu quả và càng uyển chuyển càng tốt. Một ngôn ngữ càng mạnh thì càng khó khăn cho ngôn ngữ lập trình đó để làm rõ ràng mọi thứ trong các chương trình (được viết trong ngôn ngữ này). Một số việc kiểm tra đã được dựa trên các công cụ bên ngoài, những công cụ như vậy được bàn đến trong phần Các công cụ kiểm tra tĩnh bên ngoài cho trình dịch.

Sự cấp phát vùng nhớ

Một vấn đề với C (và đây thường là vấn đề lớn cho những người mới làm quen với C) là việc cấp phát (vùng nhớ) một cách tự động hay một cách động cho các đốì tượng mà không khởi động chúng. Các đối tượng này, ban đầu, chứa các giá trị bất kì trong khoảng nhớ mà chúng được cấp phát. Các giá trị này có thể là các giá trị ngẫu nhiên còn lại trong bộ nhớ mà chưa được làm sạch, chúng hoàn toàn không dự đoán được. Nếu một chương trình có khai biến mà lại không gán giá trị ban đầu, thường là 0 (cho kiểu số) hay null (cho kiểu con trỏ) hay "" (cho kiểu dãy ký tự,...) thì có thể gây ra các phản ứng không lường trước được của chương trình đó. Hầu hết các trình dịch C hiện đại có thể phát hiện và cảnh cáo về việc "quên gán giá trị khởi động" trong nhiều trường hợp, nhưng cũng không hoàn toàn hiệu quả.

Một vấn đề thường thấy khác là bộ nhớ heap không thể được tái dụng cho tới khi nó được hoàn trả lại về cho bộ nhớ bởi người lập trình bằng câu lệnh free(). Hậu quả là nếu người lập trình quên hoàn trả các vùng đã cấp phát về cho bộ nhớ và lại tiếp tục dùng các lệnh cấp phát, thì càng lúc càng nhiều các phần của bộ nhớ bị chiếm chỗ. Lỗi này là một loại lỗi kiểu memory leak tức là "rỉ bộ nhớ". Ngược lại, cũng có trường hợp trả tự do phần đã cấp phát về cho bộ nhớ quá sớm và lại tiếp tục sử dụng vùng nhớ đã trả về thì cũng có thể dễ gây ra việc nhận sai các giá trị hay tạo ra các tình huống không lường trước được. Lý do là vì máy tính khi nhận lại các vùng đã được trả sẽ có thể dùng vùng nhớ đó cho các việc khác. Một số ngôn ngữ xử lý chuyện này với việc tự động dọn rác.

Các con trỏ

Các con trỏ là một nguồn gốc chính của nhiều nguy hiểm bởi vì chúng không được kiểm tra, một con trỏ có thể được tạo ra để chỉ tới bất kì đối tượng nào bất kể kiểu nào, kể cả các mã (nhị phân), và khi được dùng đến (hay được viết ra), có thể gây ra các hiệu ứng không lường trước được. Mặc dù hầu hết các con trỏ thường chỉ tới các chỗ an toàn, chúng vẫn có thể di chuyển tới những chỗ không an toàn như khi dùng các phép toán số học trên các con trỏ (thường là cộng trừ trên các địa chỉ mà chúng chỉ tới), vùng nhớ chỗ chúng chỉ tới có thể đã được trả về và đã được tái dụng (con trỏ đu đưa), chúng có thể đã không được khởi động (con trỏ hoang), hay chúng được trực tiếp gán một giá trị nào đó qua việc dùng toán tử đổi kiểu (cast) hoặc được gán qua một con trỏ đã bị hủy hoại. Một vấn đề khác với các con trỏ là việc C cho phép tự do chuyển đổi giữa hai kiểu con trỏ bất kì. Các ngôn ngữ khác điều chỉnh các vấn đề này bằng cách dùng các kiểu tham chiếu bị giới hạn hơn.

Các mảng

Mặc dù C có hỗ trợ riêng cho các mảng tĩnh, nhưng nó không kiểm tra xem các chỉ số mảng có hợp lệ hay không (kiểm tra biên). Thí dụ, người ta có thể viết phần tử thứ sáu của một mảng được định nghĩa với 5 phần tử, và điều này có thể mang lại các hậu quả không mong muốn. Lỗi này thuộc loại lỗi tràn bộ nhớ đệm. Đây là nguồn gốc của nhiều lỗ hổng an ninh trong các chương trình viết bằng C. Mặt khác, do sự giới hạn về kỹ thuật kiểm tra biên ở thời điểm C ra đời (khi gần như chưa có kỹ thuật kiểm tra biên), nên việc kiểm tra biên trở nên ảnh hưởng nặng đến tốc độ thực thi, đặc biệt là trong các tính toán số.

Các mảng đa chiều rất cần thiết khi cài đặt các thuật toán số (chủ yếu áp dụng cho đại số tuyến tính) để chứa các ma trận. Nhưng cấu trúc mảng theo C không những không đáp ứng mà còn không tương hợp cho thao tác chuyên biệt này. Vấn đề này đã được bàn thảo trong sách Numerical Recipes in C, chương 1.2, trang 20 ff (đọc trực tuyến). Người ta có thể tìm thấy ở đây một giải pháp tốt được dùng xuyên suốt trong cả cuốn sách này.

Các hàm tham lượng động

Một vấn đề thường thấy khác là về các hàm tham lượng động (variadic function), tức là, các hàm mà có thể thay đổi được số lượng của các tham số. Không giống như các nguyên mẫu khác của hàm trong C, kiểm tra số lượng tham số ở thời điểm dịch là không bắt buộc bởi tiêu chuẩn, và một cách tổng quát là không thể kiểm tra được nếu không có thêm thông tin. Nếu dữ liệu có kiểu không đúng được chuyển vào, thì hậu quả sẻ không lường được, và thường tạo sự hư hại hoàn toàn. Các hàm tham lượng động cũng xử lý các hằng số con trỏ rỗng trong một cách không biết trước được.

Thí dụ: Họ các hàm printf cung cấp bởi thư viện chuẩn, được dùng để định dạng các dòng chữ xuất ra, thì có tiếng vì các lỗi trong giao diện tham lượng động của nó; nó dựa trên một sự định dạng của dãy ký tự để biểu trưng số và kiểu của các tham số theo sau.

Mặc dù kiểm tra kiểu của các hàm tham lượng động từ thư viện chuẩn là một vấn đề về chất lượng của sự thiết lập, nhiều trình dịch hiện đại đặc biệt tiến hành kiểm tra kiểu của việc gọi printf, và sản sinh ra các cảnh cáo nếu danh mục tham số mà không tương ứng với dãy ký tự định dạng. Dẫu sao thì không phải tất cả các lần gọi printf đều có thể được kiểm tra một cách tĩnh bởi vì có thể dãy ký tự định dạng chỉ được lập thành ở thời gian thực thi, khi mà các hàm tham lượng động thường vẫn không kiểm tra được.

Tài liệu tham khảo

WikiPedia: C_(ngôn_ngữ_lập_trình) http://www.csse.monash.edu.au/~damian/papers/HTML/... http://www.research.att.com/~bs/bs_faq.html#really... http://www.research.att.com/~bs/sibling_rivalry.pd... http://cm.bell-labs.com/cm/cs/who/dmr/chist.html http://cm.bell-labs.com/cm/cs/who/dmr/spacetravel.... http://www-106.ibm.com/developerworks/linux/librar... http://homepage.ntlworld.com/dmjones/cbook1_0a.pdf http://david.tribble.com/text/cdiffs.htm http://www.library.cornell.edu/nr/bookcpdf/c1-2.pd... http://catalogue.bnf.fr/ark:/12148/cb119665180